﻿using System;
using System.IO;
using Sustainalytics.ESGRatings.ManagedCuda;
using Sustainalytics.Utils;
using Xunit;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing.Tests
{

    public class ESGContextTests:Disposable
    {
        ESGData currentTestSet;
        
        //public TestContext TestContext { get; set; }

        public  ESGContextTests()
        {
            currentTestSet = null;
        }

        public void Cleanup()
        {
            //if (TestContext.CurrentTestOutcome != UnitTestOutcome.Passed)
            //{
            //    if (currentTestSet != null)
            //    {
            //        string dir = @"..\..\..\FailedTests\" + (Environment.Is64BitProcess ? "x64" : "x86");
            //        if (!Directory.Exists(dir))
            //            Directory.CreateDirectory(dir);

            //        string name = GetNonConflictingFileName(dir + @"\{0}_esgData_{1}.dat");
            //        if (name == null)
            //            throw new Exception("Cannot find a name to save test data");

            //        using (var fs = new FileStream(name, FileMode.CreateNew))
            //        {
            //            BinaryFormatter serializer = new BinaryFormatter();
            //            serializer.Serialize(fs, currentTestSet);
            //        }
            //    }
            //}

        }

        private string GetNonConflictingFileName(string formatName)
        {
            for (int i = 0; i < 1000; i++)
            {
                string name = string.Format(formatName, DateTime.UtcNow.ToString("yyyy_MM_dd"), i);
                if (!File.Exists(name))
                    return name;
            }

            return null;
        }


        [Fact]
        public void WhenOneGroup_And_NoFilter_TheRankIsOne()
        {
            int CLUSTERS_COUNT = 8;
            using (var context = new ESGContext())
            {
                var data = new ESGData(context, 5119, 0);
                for (int i = 0; i < data.companiesCount; i++)
                {
                    data.universe[i] = 1;
                    data.attributes[i] = 1;
                }

                data.PushData();
                var result = data.Ratings(127, false);

                
                for(int i=0;i<CLUSTERS_COUNT;i++)
                {
                    for (int j = 0; j < data.companiesCount; j++)
                    {
                        if (i < 1 || i > 3) //ESG == NAN  
                            Assert.Equal(1, result.Rankings[i * data.companiesCount + j]);
                        else
                        {
                            Assert.Equal(-2, result.Rankings[i * data.companiesCount + j]);
                        }
                    }
                }
             
            }
                
        }


        [Fact]
        public void WhenOneGroups_And_NoFilter_AndRelativeWeightZero_TheRankIsOne()
        {
            int CLUSTERS_COUNT = 8;
            using (var context = new ESGContext())
            {
                var data = new ESGData(context, 2 , new int [] { 1 });
                for (int i = 0; i < data.companiesCount; i++)
                {
                    data.universe[i] = 1;
                    data.attributes[i] = 1;
                }

                for (int i = 0; i < data.companiesCount; i++)
                {
                    data.SetProfileFlag(i,1);
                    for (int k = 0; k < data.indicatorsCount; k++)
                    {
                        data.SetScore(i, k, i);
                        data.SetRelativeWeight(i, k, 0);
                    }
                }

                data.PushData();
                var result = data.Ratings(127, false);


                for (int i = 0; i < CLUSTERS_COUNT; i++)
                {
                    for (int j = 0; j < data.companiesCount; j++)
                    {
                        if (i < 1 || i > 3) //ESG == NAN  
                            Assert.Equal(1, result.Rankings[i * data.companiesCount + j]);
                        else
                        {
                            Assert.Equal(-2, result.Rankings[i * data.companiesCount + j]);
                        }
                    }
                }

            }

        }



        [Fact]
        public void WhenOneGroup_AllFiltered_RankIsMinusOne()
        {
            int CLUSTERS_COUNT = 8;
            using (var context = new ESGContext())
            {
                byte filter = 0;
                var data = new ESGData(context, 5119, 0);
                for (int i = 0; i < data.companiesCount; i++)
                {
                    data.universe[i] = 1;
                    data.attributes[i] = 1;
                }


                for (int i = 0; i < data.companiesCount; i++)
                {
                    data.SetProfileFlag(i,1);
                    for (int k = 0; k < data.indicatorsCount; k++)
                    {
                        data.SetScore(i, k, i + 1);
                        data.SetRelativeWeight(i, k, 1);
                    }
                }

                data.PushData();
                var result = data.Ratings(filter, false);


                for (int i = 0; i < CLUSTERS_COUNT; i++)
                {
                    for (int j = 0; j < data.companiesCount; j++)
                    {
                        Assert.Equal(-1, result.Rankings[i * data.companiesCount + j]);
                    }
                }

            }

        }


        [Fact]
        public void WhenOneGroup_And_NoFilter_OneIndicatorIncrementedByOne()
        {
            int CLUSTERS_COUNT = 8;
            for (int k = 0; k < 150; k++)
            {
                using (var context = new ESGContext())
                {
                    var data = new ESGData(context, 2, 0);
                    float[] referenceData = new float[data.companiesCount];
                    for (int i = 0; i < data.companiesCount; i++)
                    {
                        data.universe[i] = 1;
                        data.attributes[i] = 1;
                        data.SetProfileFlag(i, 1);
                    }


                    for (int i = 0; i < data.companiesCount; i++)
                    {
                       
                        data.SetScore(i, k, i);
                        data.SetRelativeWeight(i, k, 1);
                     
                        referenceData[data.companiesCount - 1 - i] = i;
                    }

                    data.PushData();
                    var result = data.Ratings(127,false);


                    for (int i = 0; i < CLUSTERS_COUNT; i++)
                    {
                        Assert.True(IsSorted(result.Ratings, i * data.companiesCount, data.companiesCount, true));
                        Assert.True(IsRanked(result.Ratings, result.Rankings, i * data.companiesCount, data.companiesCount));
                    }
                    
                    Assert.True(AreEqual(result.Ratings, 0, data.companiesCount, referenceData));
                }
            }
        }


        [Fact]
        public void WhenOneGroup_RandomData_100_Times()
        {
            int CLUSTERS_COUNT = 8;

            for (int repeatTest = 0; repeatTest < 100; repeatTest++)
            {
                using (var context = new ESGContext())
                {
                    var data = new ESGData(context, 5128, 0);

                    float[] referenceData = new float[data.companiesCount];
                    for (int i = 0; i < data.companiesCount; i++)
                    {
                        data.universe[i] = 1;
                        data.attributes[i] = 1;
                    }

                    int seed = (int)(DateTime.Now.Ticks % 10e+10);
                    Random random = new Random(seed);
                    for (int i = 0; i < data.companiesCount; i++)
                    {
                        data.SetProfileFlag(i,1);
                        for (int k = 0; k < data.indicatorsCount; k++)
                        {
                            data.SetScore(i, k, random.Next(1, 100));
                            data.SetRelativeWeight(i, k, (float)random.NextDouble());
                        }
                    }

                    this.currentTestSet = data;
                    data.PushData();
                    CPUvsGPU(CLUSTERS_COUNT, data);
                }
            }
        }


        [Fact]
        public void WhenOneGroups_And_HalfFilter()
        {
            int CLUSTERS_COUNT = 8;
            using (var context = new ESGContext())
            {
                var data = new ESGData(context, 2, new int[] { });
                for (int i = 0; i < data.companiesCount; i++)
                {
                    data.universe[i] = 1;
                    data.attributes[i] = (byte)(i + 1); // 1 and 2
                }

                for (int i = 0; i < data.companiesCount; i++)
                {
                    for (int k = 0; k < data.indicatorsCount; k++)
                    {
                        data.SetScore(i, k, i + 1);
                        data.SetRelativeWeight(i, k, 1);
                    }
                }

                data.PushData();
                var result = data.Ratings(2, false); //include only companies with 1 attribute


                for (int i = 0; i < CLUSTERS_COUNT; i++)
                {

                    int j = 0;
                    Assert.Equal(1, result.Rankings[i * data.companiesCount + j]);
                    Assert.NotEqual(-1, result.Ratings[i * data.companiesCount + j]);

                    Assert.Equal(-1, result.Rankings[i * data.companiesCount + j + 1]);
                    Assert.NotEqual(-1, result.Ratings[i * data.companiesCount + j + 1]); // the rating is calculated for the filtered companies
                }

            }


        }


        public void CPUvsGPU(int CLUSTERS_COUNT, ESGData data)
        {

            var companyRatingResult = new ESGCpuComputation().CpuRatings(data);

            var result = data.Ratings(127, true);

            for (int i = 0; i < CLUSTERS_COUNT; i++)
            {
                Assert.True(IsSorted(result.Ratings, i * data.companiesCount, data.companiesCount, true));
                Assert.True(IsRanked(result.Ratings, result.Rankings, i * data.companiesCount, data.companiesCount));
            }

            for (int j = 0; j < data.companiesCount; j++)
            {
                for (int k = 0; k < data.indicatorsCount; k++)
                {
                    double diff = Math.Abs(data.GetIndicatorRating(j, k) - result.IndicatorsRatings[k * data.companiesCount + j]);
                    Assert.True(diff < 1.0e-6);
                }
            }


            Func<int, float> cudaIndicesToRating = (i) =>
            {
                int companyIndex = result.CompanyIndices[i];
                return companyRatingResult[i / data.companiesCount].Results[companyIndex];
            };


            Assert.True(CompareL2(cudaIndicesToRating, i => result.Ratings[i], CLUSTERS_COUNT * data.companiesCount, 1.0e-6));
        }

         
        public bool CompareL2(Func<int,float> referenceData,Func<int,float> computedData, int len, double epsilon)
        {
            double error = 0;
            float reference2 = 0;
            
            for (int i = 0; i < len; i++)
            {
                var reference = referenceData(i);
                var data = computedData(i);
                double diff = reference - data;
                error += diff * diff;
                Assert.True(error < 1);
                reference2 += reference * reference;
            }

            double normRef = Math.Sqrt(reference2);

            if (Math.Abs(reference2) < 1e-7)
            {
                    return false;
            }
    

            double normError = Math.Sqrt(error);
            error = normError / normRef;
            bool result = error < epsilon;
            
            return result;
        }
       
  
        private bool AreEqual(float[] values, int startIncluse, int count, float[] referenceData)
        {
            for (int i = 0; i < count; i++)
            {
                int index = startIncluse + i;
                if (referenceData[i] != values[i])
                    return false;
            }
            return true;
        }

        private bool IsSorted(float[] values, int startIncluse, int count , bool desc)
        {
            if (count <= 1)
                return true;

            for (int i = 1; i < count; i++)
            {
                if (!desc)
                {
                    if (values[startIncluse + i] < values[startIncluse + i - 1])
                        return false;
                }
                else
                {
                    if (values[startIncluse + i] > values[startIncluse + i - 1])
                        return false;
                }
            }

            return true;
        }

        private bool IsRanked(float[] values, int[] ranks, int startIncluse, int count)
        {
            if (count <= 0)
                return true;
            
            int rank = 0;
            int computedRank = 0;
            for (int i = 0; i < count; i++)
            {
                int index = startIncluse + i;
                if (float.IsNaN(values[index]))
                {
                    if (ranks[index] != -2)
                        return false;
                    
                    continue;
                }
                else
                    if (values[index] == -1)
                    {
                        if (ranks[index] != -1)
                            return false;

                        continue;
                    }
                    else
                    {
                        ++rank;
                    }

                if (i>0 && values[index] == values[index - 1])
                {
                    if (ranks[index] != ranks[index - 1])
                        return false;
                }
                else
                {
                    computedRank = rank;
                }

                if (ranks[index] != computedRank)
                    return false;
                
            }

            return true;
        }
   
    }
}
